﻿using System.ServiceModel.Syndication;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.IO;
using System.Xml;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System;
using System.ServiceModel.Channels;
using System.Xml.Linq;
using System.Xml.Schema;
using System.ServiceModel.Web;
using System.Web.Caching;

namespace dasBlog.Storage
{
    [ServiceBehavior(ConfigurationName = "StorageNode", InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    internal sealed class StorageNodeService<T> : IStorageNode where T : class, IEntry<T>, new()
    {
        private IStorageProvider<T> _provider;

        public StorageNodeService(IStorageProvider<T> provider)
        {
            _provider = provider;
        }

        public StorageNodeService(Type providerType, string initData)
        {
            _provider = (IStorageProvider<T>)Activator.CreateInstance(providerType);
            if (_provider is IInitStorageProvider)
            {
                ((IInitStorageProvider)_provider).Initialize(initData);
            }
        }

        public Message GetItemSchema(string baseUri, string schemaId)
        {
            var replyAction = Names.IStorageNodeNamespace+"GetItemSchemaResponse";
            XmlSchema schema = SerializationTools<T>.GetSchema(baseUri, schemaId);
            if (schema != null)
            {
                return Message.CreateMessage(
                                GetReplyMessageVersion(),
                                replyAction,
                                new XmlSchemaBodyWriter(schema));
            }
            else
            {
                throw new FaultException<StorageNodeInvalidSchemaId>(
                    new StorageNodeInvalidSchemaId { SchemaId = schemaId });
            }
        }


        public Message Select(string baseUri, Moniker moniker, MonikerMatch contextMatch, QueryDescription query, SerializationMode serializationMode)
        {
            var replyAction = Names.IStorageNodeNamespace+"SelectResponse";
            XmlDictionaryReader cachedReader = null;
            string cacheKey = CacheUtil.MakeQueryKey(moniker, query, serializationMode);
            if ((cachedReader = Cache.GetReader(cacheKey)) != null)
            {
                return Message.CreateMessage(
                                            GetReplyMessageVersion(),
                                            replyAction,
                                            cachedReader);
            }
            else
            {
                var result = _provider.Select(moniker, contextMatch, query);
                if (serializationMode == SerializationMode.Rss || serializationMode == SerializationMode.Atom)
                {
                    List<SyndicationItem> items = new List<SyndicationItem>();
                    foreach (SyndicationItem item in from T t in result select t.ToSyndicationItem<T>())
                    {
                        List<SyndicationLink> links = new List<SyndicationLink>();
                        foreach (SyndicationLink link in item.Links)
                        {
                            if (link.Uri.Scheme == Moniker.Scheme)
                            {
                                links.Add(
                                    new SyndicationLink(
                                        new Moniker(link.Uri).ToUri(new Uri(baseUri)),
                                        link.RelationshipType,
                                        link.Title,
                                        link.MediaType,
                                        link.Length));
                            }
                        }
                        item.Links.Clear();
                        foreach (var link in links)
                        {
                            item.Links.Add(link);
                        }
                        if (item.Id != null)
                        {
                            item.Id = new Moniker(item.Id).ToUri(new Uri(baseUri)).ToString();
                        }
                        items.Add(item);
                    }

                    var feed = new SyndicationFeed(items);
                    feed.Generator = "dasBlog Storage";
                    if (serializationMode == SerializationMode.Rss)
                    {
                        cachedReader = Cache.AddFeed(cacheKey, new Rss20FeedFormatter<SyndicationFeed>(feed));
                        return Message.CreateMessage(
                                        GetReplyMessageVersion(),
                                        replyAction,
                                        cachedReader);
                    }
                    else
                    {
                        cachedReader = Cache.AddFeed(cacheKey, new Atom10FeedFormatter<SyndicationFeed>(feed));
                        return Message.CreateMessage(
                                        GetReplyMessageVersion(),
                                        replyAction,
                                        cachedReader);
                    }
                }
                else
                {
                    cachedReader = Cache.AddReader(cacheKey,
                                        SerializationTools<T>.SerializeList(result),
                                        moniker.ItemPath,
                                        CacheItemPriority.Normal);
                    return Message.CreateMessage(
                                   GetReplyMessageVersion(),
                                   replyAction,
                                   cachedReader );
                }
            }
        }

        public Message Get(string baseUri, Moniker itemId, SerializationMode serializationMode)
        {
            var replyAction = Names.IStorageNodeNamespace+"GetResponse";
            XmlDictionaryReader cachedReader = null;
            string cacheKey = CacheUtil.MakeQueryKey(itemId, null, SerializationMode.Native);
            if ((cachedReader = Cache.GetReader(cacheKey)) != null)
            {
                return Message.CreateMessage(
                                            GetReplyMessageVersion(),
                                            replyAction,
                                            cachedReader);
            }
            else
            {
                var item = _provider.Get(itemId);
                if (item != null)
                {
                    XmlDictionaryReader messageBody =
                        Cache.AddReader(cacheKey,
                                        SerializationTools<T>.Serialize(item),
                                        itemId.ItemPath,
                                        CacheItemPriority.Normal);

                    return Message.CreateMessage(
                          GetReplyMessageVersion(),
                          replyAction,
                          messageBody);
                }
                else
                {
                    return Message.CreateMessage(
                                            GetReplyMessageVersion(),
                                            replyAction);
                }
            }
        }

        public void Delete(Moniker itemId)
        {
            Cache.RemoveByKeyPrefix(itemId.ItemPath);
            
            _provider.Delete(itemId);
        }

        public Moniker Store(Moniker moniker, XmlElement xitem, SerializationMode serializationMode)
        {
            Cache.RemoveByKeyPrefix(moniker.ItemPath);

            return _provider.Store(moniker, SerializationTools<T>.Deserialize(new XmlNodeReader(xitem), serializationMode == SerializationMode.Json ? WebMessageFormat.Json : WebMessageFormat.Xml));
        }

        public Message Aggregate(string baseUri, Moniker moniker, MonikerMatch match, string propertyName, SerializationMode serializationMode)
        {
            var replyAction = Names.IStorageNodeNamespace+"AggregateResponse";
            XmlDictionaryReader cachedReader = null;
            string cacheKey = CacheUtil.MakeAggregateKey(moniker, propertyName, serializationMode);
            if ((cachedReader = Cache.GetReader(cacheKey)) != null)
            {
                return Message.CreateMessage(
                                            GetReplyMessageVersion(),
                                            replyAction,
                                            cachedReader);
            }

            var result = _provider.Aggregate(moniker, match, propertyName);
            if (result == null || result.Count() == 0)
            {
                return Message.CreateMessage(
                                        GetReplyMessageVersion(),
                                        replyAction);
            }
            else
            {
                if (serializationMode == SerializationMode.Rss || serializationMode == SerializationMode.Atom)
                {
                    var feed = new SyndicationFeed(from PropertyAggregate prop in result select new SyndicationItem { Title = TextSyndicationContent.CreatePlaintextContent(prop.Value), Content = TextSyndicationContent.CreatePlaintextContent(prop.Count.ToString()) });
                    if (serializationMode == SerializationMode.Rss)
                    {
                        cachedReader = Cache.AddFeed(cacheKey, new Rss20FeedFormatter<SyndicationFeed>(feed));
                        return Message.CreateMessage(
                                        GetReplyMessageVersion(),
                                        replyAction,
                                        cachedReader);
                    }
                    else
                    {
                        cachedReader = Cache.AddFeed(cacheKey, new Atom10FeedFormatter<SyndicationFeed>(feed));
                        return Message.CreateMessage(
                                        GetReplyMessageVersion(),
                                        replyAction,
                                        cachedReader);
                    }
                }
                else
                {

                    cachedReader = Cache.AddReader(cacheKey,
                                        SerializationTools<PropertyAggregate>.SerializeList(result),
                                        moniker.ItemPath,
                                        CacheItemPriority.Normal);

                    return Message.CreateMessage(
                       GetReplyMessageVersion(),
                       replyAction,
                       cachedReader);
                }
            }
        }

        private static MessageVersion GetReplyMessageVersion()
        {
            if (OperationContext.Current != null)
            {
                return OperationContext.Current.IncomingMessageVersion;
            }
            else
            {
                return MessageVersion.Default;
            }
        }

        private static System.Web.Caching.Cache _cache = null;
        private System.Web.Caching.Cache Cache
        {
            get
            {
                if (System.Web.Hosting.HostingEnvironment.Cache != null)
                {
                    return System.Web.Hosting.HostingEnvironment.Cache;
                }
                else if ( _cache == null )
                {
                    _cache = new System.Web.Caching.Cache();
                }
                return _cache;
            }
        }

    }
}
